Scrapbox記法をBlock kitに変換するscript
/icons/Google Apps Script.icon用にしてある
ESModuleで使うときは、先頭にimport {ScrapboxParser} from '/path/to/parser.js';などと追記すること
code:sb2mrkdwn.gs(js)
function sb2mrkdwn(text, { project }) {
const blocks = ScrapboxParser.parse(text, { hasTitle: false });
return blocks.map(block => convertSb2Md(block, { project }));
}
function convertSb2Md(block, { project }) {
switch (block.type) {
case 'title':
return; // タイトルは選択範囲に入らないので無視
case 'codeBlock':
return {
type: 'section',
text: {
type: 'mrkdwn',
text: \`${block.fileName}\`\n\`\`\`${block.content}\`\`\`,
},
};
case 'table':
return {
type: 'section',
text: {
type: 'mrkdwn',
text: \`table:${block.fileName}\`,
},
};
case 'line':
return convertLine(block, { project });
}
}
function convertLine(line, { project }) {
const objects = line.nodes
.flatMap(node => convertNode(node, { project }));
//console.log(objects);
// 画像のみの行は大きく表示する。アイコンは除く
if (objects.length === 1
&& objects0 && objects0.type === 'image' && !/^https:\/\/scrapbox\.io\/api\/pages\/.+\/.+\/icon$/.test(objects0)) return objects0; // elementsの上限を越えないように、mrkdwnを結合する
//全部結合するのではyyなく、上限を越える場合のみ結合するようにする?
const elements = [];
let chunk = undefined;
for (const object of objects) {
if (object.type !== 'mrkdwn') {
if (chunk) elements.push(chunk);
chunk = undefined;
elements.push(object);
continue;
}
chunk = {
type: 'mrkdwn',
text: ${chunk ? chunk.text : ''}${object.text},
};
}
if (chunk) elements.push(chunk);
if (elements.length === 0) elements.push({
type: 'plain_text',
text: ' ', // 空文字列だとエラーになる
});
return {
type: 'context',
elements,
};
}
function convertNode(node, { project }) {
switch (node.type) {
case 'quote':
const objects = node.nodes.flatMap(node => convertNode(node, { project }));
case 'plain_text':
case 'mrkdwn':
return [{
type: 'mrkdwn',
text: >${objects[0].text},
}, ...objects.slice(1)];
default:
return objects;
}
case 'image':
case 'strongImage':
return [{
type: 'image',
image_url: node.src,
alt_text: 'image',
}]
case 'icon':
case 'strongIcon':
/*const path = (() => {
if (node.pathType === 'relative') {
return /${project}/${encodeURIComponent(node.path)};
} else {
const root = node.path.split('/')1; const iconName = node.path.slice(/${root}/.length);
return /${root}/${encodeURIComponent(iconName)};
}
})();*/
const path = node.pathType === 'relative' ? /${project}/${node.path} : node.path;
return [{
type: 'image',
image_url: https://scrapbox.io/api/pages${path}/icon,
alt_text: node.path,
}];
case 'formula':
return [{
type: 'mrkdwn',
text: \`${node.formula}\`,
}];
case 'helpfeel':
return [{
type: 'mrkdwn',
text: \`? ${node.text}\`,
}];
case 'commandLine':
return [{
type: 'mrkdwn',
text: \`${node.symbol} ${node.text}\`,
}];
case 'decoration':
const hasStrong = node.decos.some(deco => /\*-/.test(deco0)); const hasItalic = node.decos.includes('/');
const hasStrike = node.decos.includes('-');
return node.nodes.flatMap(node => convertNode(node, { project }))
.map(object => {
if (object.type !== 'plain_text'
|| !/^\s*$/.test(object.text)
|| !/^<https?:\/\/.+\|.+>$/.test(object.text)) return object;
let text = object.text;
if (hasStrong) text = *${text}*;
if (hasItalic) text = _${text}_;
if (hasStrike) text = ~${text}~;
return {
type: 'mrkdwn',
text: text,
};
});
case 'strong':
return node.nodes.flatMap(node => convertNode(node, { project }))
.map(object => {
if (object.type !== 'plain_text') return object;
return {
type: 'mrkdwn',
text: *${object.text}*,
};
});
case 'code':
return [{
type: 'mrkdwn',
text: \`${node.text}\`,
}];
case 'googleMap':
return [{
type: 'mrkdwn',
text: <${node.url}|${node.place}>,
}];
case 'link':
switch (node.pathType) {
case 'root':
return [{
type: 'mrkdwn',
text: <https://scrapbox.io${node.href}|${node.href}>,
}];
case 'relative':
return [{
type: 'mrkdwn',
text: <https://scrapbox.io/${project}/${node.href}|${node.href}>,
}];
case 'absolute':
return [{
type: 'mrkdwn',
text: <${node.href}|${node.content || node.href}>,
}];
}
case 'hashTag':
return [{
type: 'mrkdwn',
text: <https://scrapbox.io/${project}/${node.href}|#${node.href}>,
}];
case 'blank':
case 'plain':
return [{
type: 'mrkdwn',
text: node.text,
}];
}
}